﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;

namespace ESpace
{
    /// <summary>
    /// класс, хранит лист объектов с возможностью перемещаться по ним
    /// </summary>
    public class History<T>
    {
        List<T> hst = new List<T>();
        List<String> cmt = new List<string>();
        int maxLen = 100;
        object btnUndo = null;
        object btnRedo = null;
        int actH = 0;

        #region *** Свойства *******************************

        public int MaxLength
        {
            get { return maxLen; }
            set
            {
                maxLen = value;
                if (maxLen < 1) throw new Exception("Ошибка объекта истории: Максимальный размер истории не может быть меньше 1");

                if (hst.Count > maxLen)
                {
                    int diff = hst.Count - maxLen;
                    actH -= diff;
                    if (actH < 0) actH = 0;
                    hst.RemoveRange(0, diff);

                    ((Control)btnUndo).Enabled = UndoIsVisible;
                    ((Control)btnUndo).Enabled = RedoIsVisible;
                }
            }
        }

        /// <summary>
        /// текущий номер элемента в списке истории
        /// при изменении номера вызывается метод JampTo и событие Jamp
        /// </summary>
        public int ActHistInd
        {
            get { return actH; }
            set { JampTo(value); }
        }

        public T ActObject
        {
            get
            {
                return hst[actH];
            }
        }

        #endregion *** Свойства *******************************

        public bool UndoIsVisible { get { return actH > 0; } }
        public bool RedoIsVisible { get { return actH < (hst.Count - 1); } }

        /// <summary>
        /// Ведение истории.
        /// Для движения вызывать методы Undo и Redo.
        /// Обработку перемещения делать в событии Jump. void Hist_Jamp(object Sender, T actObj, String actComment)
        /// </summary>
        /// <param name="MaxLen">максимальная длина истории</param>
        /// <param name="ActObj">дубликат текущего (самого первого) объекта</param>
        public History(int MaxLen, T ActObj)
        {
            maxLen = MaxLen;
            hst.Add(ActObj);
            cmt.Add("");
        }

        /// <summary>
        /// Ведение истории.
        /// Для движения вызывать методы Undo и Redo.
        /// Обработку перемещения делать в событии Jump. void Hist_Jamp(object Sender, T actObj, String actComment)
        /// </summary>
        /// <param name="MaxLen">максимальная длина истории</param>
        /// <param name="ActObj">дубликат текущего (самого первого) объекта</param>
        /// <param name="ButtonUndo">кнопка назад (если ToolStripSplitButton, будет выпадающее меню истории)</param>
        /// <param name="ButtonRedo">кнопка вперед (если ToolStripSplitButton, будет выпадающее меню истории)</param>
        public History(int MaxLen, T ActObj, object ButtonUndo, object ButtonRedo)
        {
            maxLen = MaxLen;
            hst.Add(ActObj);

            btnUndo = ButtonUndo;
            btnRedo = ButtonRedo;

            SetEnableButtons();

            cmt.Add("");
        }

        /// <summary>
        /// Ведение истории.
        /// Для движения вызывать методы Undo и Redo.
        /// Обработку перемещения делать в событии Jump. void Hist_Jamp(object Sender, T actObj, String actComment)
        /// </summary>
        /// <param name="MaxLen">максимальная длина истории</param>
        /// <param name="ActObj">дубликат текущего (самого первого) объекта</param>
        /// <param name="Comment">Коментарий к действию</param>
        public History(int MaxLen, T ActObj, String Comment)
        {
            maxLen = MaxLen;
            hst.Add(ActObj);
            cmt.Add(Comment);
        }

        /// <summary>
        /// Ведение истории.
        /// Для движения вызывать методы Undo и Redo.
        /// Обработку перемещения делать в событии Jump. void Hist_Jamp(object Sender, T actObj, String actComment)
        /// </summary>
        /// <param name="MaxLen">максимальная длина истории</param>
        /// <param name="ActObj">дубликат текущего (самого первого) объекта</param>
        /// <param name="ButtonUndo">кнопка назад (если ToolStripSplitButton, будет выпадающее меню истории)</param>
        /// <param name="ButtonRedo">кнопка вперед (если ToolStripSplitButton, будет выпадающее меню истории)</param>
        /// <param name="Comment">Коментарий к действию</param>
        public History(int MaxLen, T ActObj, object ButtonUndo, object ButtonRedo, String Comment)
        {
            maxLen = MaxLen;
            hst.Add(ActObj);

            btnUndo = ButtonUndo;
            btnRedo = ButtonRedo;

            SetEnableButtons();

            cmt.Add(Comment);
        }

        void SetEnableButtons()
        {
            if (btnUndo != null)
            {
                Type tp = btnUndo.GetType();
                if (tp == typeof(Button)) ((Button)btnUndo).Enabled = UndoIsVisible;
                if (tp == typeof(ToolStripButton)) ((ToolStripButton)btnUndo).Enabled = UndoIsVisible;
                if (tp == typeof(ToolStripDropDownButton)) ((ToolStripDropDownButton)btnUndo).Enabled = UndoIsVisible;
                if (tp == typeof(ToolStripSplitButton)) ((ToolStripSplitButton)btnUndo).Enabled = UndoIsVisible;
            }

            if (btnRedo != null)
            {
                Type tp = btnRedo.GetType();
                if (tp == typeof(Button)) ((Button)btnRedo).Enabled = RedoIsVisible;
                if (tp == typeof(ToolStripButton)) ((ToolStripButton)btnRedo).Enabled = RedoIsVisible;
                if (tp == typeof(ToolStripDropDownButton)) ((ToolStripDropDownButton)btnRedo).Enabled = RedoIsVisible;
                if (tp == typeof(ToolStripSplitButton)) ((ToolStripSplitButton)btnRedo).Enabled = RedoIsVisible;
            }

            ReFillComments();
        }

        /// <summary>
        /// Запись нового элемента истории. При записи, необходимо клонировать объект
        /// Записный элемент становится текущим
        /// </summary>
        /// <param name="obj">объект для записи в историю</param>
        public void RecHist(T obj)
        {
            RecHist(obj, "");
        }

        public void RecHist(T obj, String Comment)
        {
            // удаление записей после текущей
            if (RedoIsVisible)
            {
                hst.RemoveRange(actH + 1, hst.Count - actH - 1);
                cmt.RemoveRange(actH + 1, cmt.Count - actH - 1);
            }

            // удаление записи, если превышено максимальное значение
            if (hst.Count == maxLen)
            {
                hst.RemoveAt(0);
                cmt.RemoveAt(0);
            }
            else actH++;
            hst.Add(obj);
            cmt.Add(Comment);
            SetEnableButtons();

        }

        public T Undo()
        {
            if (UndoIsVisible)
            {
                actH--;
                SetEnableButtons();
                OnUndoRedoJamp();
            }
            return hst[actH];
        }

        public T Redo()
        {
            if (RedoIsVisible)
            {
                actH++;
                SetEnableButtons();
                OnUndoRedoJamp();
            }
            return hst[actH];
        }

        public T JampTo(int n)
        {
            if (n >= 0 && n < hst.Count)
            {
                actH = n;
                SetEnableButtons();
                OnJamp();
                OnUndoRedoJamp();
            }
            return hst[actH];
        }

        public void Clear(T ActObj)
        {
            Clear(ActObj, "");
        }

        public void Clear(T ActObj, String Comment)
        {
            hst.Clear();
            cmt.Clear();

            hst.Add(ActObj);
            cmt.Add("");

            actH = 0;

            SetEnableButtons();
        }

        public String ActComment
        {
            get
            {
                return cmt[actH];
            }
        }

        public String[] GetCommentArray()
        {
            return cmt.ToArray();
        }

        void ReFillComments()
        {
            if (typeof(ToolStripSplitButton) == btnUndo.GetType())
            {
                ToolStripSplitButton mBtnUndo = (ToolStripSplitButton)btnUndo;
                // очищаем
                mBtnUndo.DropDownItems.Clear();

                for (int i = (actH - 1); i >= 0; i--)
                {
                    ToolStripMenuItem tsmi = new ToolStripMenuItem(cmt[i]);
                    tsmi.Name = "tiUndo" + i.ToString();
                    tsmi.Size = new System.Drawing.Size(152, 22);
                    tsmi.Click += new System.EventHandler(tsmi_Click);

                    mBtnUndo.DropDownItems.Add(tsmi);
                }
            }

            if (typeof(ToolStripDropDownButton) == btnUndo.GetType())
            {
                ToolStripDropDownButton mBtnUndo = (ToolStripDropDownButton)btnUndo;
                // очищаем
                mBtnUndo.DropDownItems.Clear();

                for (int i = (actH - 1); i >= 0; i--)
                {
                    ToolStripMenuItem tsmi = new ToolStripMenuItem(cmt[i]);
                    tsmi.Name = "tiUndo" + i.ToString();
                    tsmi.Size = new System.Drawing.Size(152, 22);
                    tsmi.Click += new System.EventHandler(tsmi_Click);

                    mBtnUndo.DropDownItems.Add(tsmi);
                }
            }

            if (typeof(ToolStripSplitButton) == btnRedo.GetType())
            {
                ToolStripSplitButton mBtnRedo = (ToolStripSplitButton)btnRedo;
                // очищаем
                mBtnRedo.DropDownItems.Clear();

                for (int i = (actH + 1); i < cmt.Count; i++)
                {
                    ToolStripMenuItem tsmi = new ToolStripMenuItem(cmt[i]);
                    tsmi.Name = "tiRedo" + i.ToString();
                    tsmi.Size = new System.Drawing.Size(152, 22);
                    tsmi.Click += new System.EventHandler(tsmi_Click);

                    mBtnRedo.DropDownItems.Add(tsmi);
                }
            }

            if (typeof(ToolStripDropDownButton) == btnRedo.GetType())
            {
                ToolStripDropDownButton mBtnRedo = (ToolStripDropDownButton)btnRedo;
                // очищаем
                mBtnRedo.DropDownItems.Clear();

                for (int i = (actH + 1); i < cmt.Count; i++)
                {
                    ToolStripMenuItem tsmi = new ToolStripMenuItem();
                    tsmi.Name = "tiRedo" + i.ToString();
                    tsmi.Text = cmt[i];
                    //tsmi.Size = new System.Drawing.Size(152, 22);
                    tsmi.Click += new System.EventHandler(tsmi_Click);

                    mBtnRedo.DropDownItems.Add(tsmi);
                }
            }
        }

        private void tsmi_Click(object sender, EventArgs e)
        {
            int n = 0;
            String s = ((ToolStripMenuItem)sender).Name;
            if (s == "") return;
            n = Convert.ToInt32(s.Substring(6, s.Length - 6));
            JampTo(n);
        }

        public delegate void HistEventDelegate(Object Sender, T el, String Comment);

        public event HistEventDelegate Jamp;
        public void OnJamp()
        {
            if (Jamp != null)
                Jamp(this, hst[actH], cmt[actH]);
        }

        public event HistEventDelegate UndoRedoJamp;
        public void OnUndoRedoJamp()
        {
            if (UndoRedoJamp != null)
                UndoRedoJamp(this, hst[actH], cmt[actH]);
        }

    }
}
